
/******************************************************************************
 * MODULE     : colors.cpp
 * DESCRIPTION: Multi format RGB based color management
 * COPYRIGHT  : (C) 2014  François Poulain, Joris van der Hoeven
 *******************************************************************************
 * This software falls under the GNU general public license version 3 or later.
 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
 ******************************************************************************/

#include "colors.hpp"
#include "analyze.hpp"
#include <string.h>

#include "dvips_colors.hpp"
#include "svg_colors.hpp"
#include "tm_colors.hpp"
#include "tm_debug.hpp"
#include "x11_colors.hpp"
#include "xc_colors.hpp"

#include <lolly/data/numeral.hpp>

using lolly::data::as_hexadecimal;
using lolly::data::from_hex;
using lolly::data::to_Hex;
#define from_hexadecimal from_hex

/******************************************************************************
 * Set up colormaps
 ******************************************************************************/

int  pastel        = 223;
bool true_colors   = true;
bool init_colors   = true;
bool reverse_colors= false;

void
set_true_colors (bool tc) {
  true_colors= tc;
}

void
set_reverse_colors (bool rc) {
  reverse_colors= rc;
}

bool
get_reverse_colors () {
  return reverse_colors;
}

static int CSCALES= 4;
static int CFACTOR= 5;
static int GREYS  = 16;
static int CTOTAL = (CFACTOR * CFACTOR * CFACTOR + GREYS + 1);

void
set_color_attrs (int cs, int cf, int gr) {
  CSCALES= cs;
  CFACTOR= cf;
  GREYS  = gr;
  CTOTAL = (CFACTOR * CFACTOR * CFACTOR + GREYS + 1);
}

void
get_color_attrs (int& cs, int& cf, int& gr, int& ct) {
  cs= CSCALES;
  cf= CFACTOR;
  gr= GREYS;
  ct= CTOTAL;
}

/******************************************************************************
 * Set up colors
 ******************************************************************************/

#ifdef QTTEXMACS
color black  = rgb_color (0, 0, 0);
color white  = rgb_color (255, 255, 255);
color red    = rgb_color (255, 0, 0);
color blue   = rgb_color (0, 0, 255);
color yellow = rgb_color (255, 255, 0);
color green  = rgb_color (0, 255, 0);
color magenta= rgb_color (255, 0, 255);
color orange = rgb_color (255, 128, 0);
color brown  = rgb_color (128, 32, 0);
color pink   = rgb_color (255, 128, 128);

color light_grey= rgb_color (208, 208, 208);
color grey      = rgb_color (184, 184, 184);
color dark_grey = rgb_color (112, 112, 112);
#else
color black, white, red, green, blue;
color yellow, magenta, orange, brown, pink;
color light_grey, grey, dark_grey;

void
initialize_colors () {
  black  = rgb_color (0, 0, 0);
  white  = rgb_color (255, 255, 255);
  red    = rgb_color (255, 0, 0);
  blue   = rgb_color (0, 0, 255);
  yellow = rgb_color (255, 255, 0);
  green  = rgb_color (0, 255, 0);
  magenta= rgb_color (255, 0, 255);
  orange = rgb_color (255, 128, 0);
  brown  = rgb_color (128, 32, 0);
  pink   = rgb_color (255, 128, 128);

  light_grey= rgb_color (208, 208, 208);
  grey      = rgb_color (184, 184, 184);
  dark_grey = rgb_color (112, 112, 112);
  pastel    = (true_colors || GREYS == 255) ? 223 : 191;
}
#endif

color tm_background= rgb_color (160, 160, 160);

/******************************************************************************
 * Reversed colors
 ******************************************************************************/

void
reverse (int& r, int& g, int& b) {
  int    m = min (r, min (g, b));
  int    M = max (r, max (g, b));
  int    t = (r + g + b) / 3;
  int    tt= 255 - t;
  double mu= 1.0;
  // tt= 6 * tt / 7;
  if (M != m) {
    double lambda1=
        max (((double) (t - m)) / t, ((double) (M - t)) / (255 - t));
    double lambda2=
        max (((double) (t - m)) / tt, ((double) (M - t)) / (255 - tt));
    mu= lambda1 / lambda2;
  }
  r= (int) (tt + mu * (r - t) + 0.5);
  g= (int) (tt + mu * (g - t) + 0.5);
  b= (int) (tt + mu * (b - t) + 0.5);
}

color
reverse (color col) {
  int r, g, b, a;
  get_rgb_color (col, r, g, b, a);
  reverse (r, g, b);
  return rgb_color (r, g, b, a);
}

/******************************************************************************
 * Blended colors
 ******************************************************************************/

color
blend_colors (color fg, color bg) {
  if (((fg >> 24) & 255) == 255) return fg;
  int fR, fG, fB, fA, bR, bG, bB, bA;
  get_rgb_color (fg, fR, fG, fB, fA);
  get_rgb_color (bg, bR, bG, bB, bA);
  int rR= (bR * (255 - fA) + fR * fA) / 255;
  int rG= (bG * (255 - fA) + fG * fA) / 255;
  int rB= (bB * (255 - fA) + fB * fA) / 255;
  int rA= (bA * (255 - fA) + fA * fA) / 255;
  return rgb_color (rR, rG, rB, rA);
}

/******************************************************************************
 * RGB interface
 ******************************************************************************/

color
rgb_color (int r, int g, int b, int a) {
  if (true_colors) {
    if (reverse_colors) reverse (r, g, b);
    return (a << 24) + (r << 16) + (g << 8) + b;
  }
  else if ((r == g) && (g == b)) return (a << 24) + ((r * GREYS + 128) / 255);
  else {
    r= (r * CSCALES + 128) / 255;
    g= (g * CSCALES + 128) / 255;
    b= (b * CSCALES + 128) / 255;
    return (a << 24) + r * CFACTOR * CFACTOR + g * CFACTOR + b + GREYS + 1;
  }
}

color
rgb_color (array<int> rgba) {
  return rgb_color (
      N (rgba) >= 1 ? rgba[0] : 255, N (rgba) >= 2 ? rgba[1] : 255,
      N (rgba) >= 3 ? rgba[2] : 255, N (rgba) >= 4 ? rgba[3] : 255);
}

void
get_rgb_color (color col, int& r, int& g, int& b, int& a) {
  if (true_colors) {
    a= (col >> 24) & 255;
    r= (col >> 16) & 255;
    g= (col >> 8) & 255;
    b= col & 255;
    if (reverse_colors) reverse (r, g, b);
  }
  else {
    a  = (col >> 24) & 255;
    col= col & 0xffffff;
    if (col <= ((color) GREYS)) {
      r= (col * 255) / GREYS;
      g= (col * 255) / GREYS;
      b= (col * 255) / GREYS;
    }
    else {
      int rr, gg, bb;
      col-= (GREYS + 1);
      bb= col % CFACTOR;
      gg= (col / CFACTOR) % CFACTOR;
      rr= (col / (CFACTOR * CFACTOR)) % CFACTOR;
      r = (rr * 255) / CSCALES;
      g = (gg * 255) / CSCALES;
      b = (bb * 255) / CSCALES;
    }
  }
}

array<int>
get_rgb_color (color col) {
  int r, g, b, a;
  get_rgb_color (col, r, g, b, a);
  array<int> ret;
  ret << r << g << b << a;
  return ret;
}

/******************************************************************************
 * CMYK interface
 ******************************************************************************/

color
cmyk_color (int c, int m, int y, int k, int a) {
  double       c_= c, m_= m, y_= y, k_= k;
  unsigned int r, g, b;
  c_= k_ + c_ * (1 - k_ / 255);
  m_= k_ + m_ * (1 - k_ / 255);
  y_= k_ + y_ * (1 - k_ / 255);

  r= round (255 - c_);
  g= round (255 - m_);
  b= round (255 - y_);
  return rgb_color (r, g, b, a);
}
// TODO
// void
// get_cmyk_color (color col, int& c, int& m, int& y, int &k, int& a) {

/******************************************************************************
 * XPM interface
 ******************************************************************************/

color
xpm_color (string s) {
  if (s == "none") return rgb_color (100, 100, 100, 0);
  if ((N (s) == 4) && (s[0] == '#')) {
    int r= 17 * from_hexadecimal (s (1, 2));
    int g= 17 * from_hexadecimal (s (2, 3));
    int b= 17 * from_hexadecimal (s (3, 4));
    return rgb_color (r, g, b);
  }
  if ((N (s) == 7) && (s[0] == '#')) {
    int r= from_hexadecimal (s (1, 3));
    int g= from_hexadecimal (s (3, 5));
    int b= from_hexadecimal (s (5, 7));
    return rgb_color (r, g, b);
  }
  if ((N (s) == 13) && (s[0] == '#')) {
    int r= from_hexadecimal (s (1, 5));
    int g= from_hexadecimal (s (5, 9));
    int b= from_hexadecimal (s (9, 13));
    return rgb_color (r, g, b);
  }
  s= locase_all (s);
  if (x11_ch->contains (s)) return x11_color (s);
  return black;
}

// TODO
// void
// get_xpm_color (color col, string s) {

/******************************************************************************
 * Named colors
 ******************************************************************************/

static void
populates_colorhash_from_rgb_record (rgb_record* rec, colorhash ch) {
  while (strcmp (rec->name, "") != 0) {
    string name (locase_all (rec->name));
    color  col= rgb_color (rec->r, rec->g, rec->b);

    if (DEBUG_STD && ch->contains (name) && ch[name] != col) {
      debug_std << "Redefined color " << name << LF << get_hex_color (ch[name])
                << " replaced by " << get_hex_color (col) << LF;
    }
    ch (name)= col;
    rec++;
  }
}

static void
populates_colorhash_from_cmyk_record (cmyk_record* rec, colorhash ch) {
  while (strcmp (rec->name, "") != 0) {
    string name (locase_all (rec->name));
    color  col= cmyk_color (rec->c, rec->m, rec->y, rec->k);

    if (DEBUG_STD && ch->contains (name) && ch[name] != col) {
      debug_std << "Redefined color " << name << LF << get_hex_color (ch[name])
                << " replaced by " << get_hex_color (col) << LF;
    }
    ch (name)= col;
    rec++;
  }
}

static color
color_from_name (string s) {
  if (N (s) > 0 && s[0] == '#') {
    if (N (s) == 4) {
      int r= 17 * from_hexadecimal (s (1, 2));
      int g= 17 * from_hexadecimal (s (2, 3));
      int b= 17 * from_hexadecimal (s (3, 4));
      return rgb_color (r, g, b);
    }
    else if (N (s) == 5) {
      int r= 17 * from_hexadecimal (s (1, 2));
      int g= 17 * from_hexadecimal (s (2, 3));
      int b= 17 * from_hexadecimal (s (3, 4));
      int a= 17 * from_hexadecimal (s (4, 5));
      return rgb_color (r, g, b, a);
    }
    else if (N (s) == 7) {
      int r= from_hexadecimal (s (1, 3));
      int g= from_hexadecimal (s (3, 5));
      int b= from_hexadecimal (s (5, 7));
      return rgb_color (r, g, b);
    }
    else if (N (s) == 9) {
      int r= from_hexadecimal (s (1, 3));
      int g= from_hexadecimal (s (3, 5));
      int b= from_hexadecimal (s (5, 7));
      int a= from_hexadecimal (s (7, 9));
      return rgb_color (r, g, b, a);
    }
  }

  if ((N (s) > 4) && (s (1, 4) == "gray") && (is_numeric (s (5, N (s))))) {
    int level, i= 5;
    if (read_int (s, i, level)) {
      level= (level * 255) / 100;
      return rgb_color (level, level, level);
    }
  }

  if (init_colors) {
    populates_colorhash_from_rgb_record (XCColors, xc_ch);
    populates_colorhash_from_rgb_record (X11Colors, x11_ch);
    populates_colorhash_from_cmyk_record (DVIPSColors, dvips_ch);
    populates_colorhash_from_rgb_record (SVGColors, svg_ch);
    populates_colorhash_from_rgb_record (TMColors, tm_ch);
    init_colors= false;
  }

  if (tm_ch->contains (s)) return tm_color (s);
  if (x11_ch->contains (s)) return x11_color (s);
  if (svg_ch->contains (s)) return svg_color (s);
  if (xc_ch->contains (s)) return xc_color (s);
  if (dvips_ch->contains (s)) return dvips_color (s);

  return black;
}

string
named_color_to_xcolormap (string s) {
  s= locase_all (s);

  if (init_colors) {
    populates_colorhash_from_rgb_record (XCColors, xc_ch);
    populates_colorhash_from_rgb_record (X11Colors, x11_ch);
    populates_colorhash_from_cmyk_record (DVIPSColors, dvips_ch);
    populates_colorhash_from_rgb_record (SVGColors, svg_ch);
    populates_colorhash_from_rgb_record (TMColors, tm_ch);
    init_colors= false;
  }

  if (xc_ch->contains (s)) return "xcolor";
  if (x11_ch->contains (s)) return "x11names";
  if (svg_ch->contains (s)) return "svgnames";
  if (dvips_ch->contains (s)) return "dvipsnames";
  if (tm_ch->contains (s)) return "texmacs";
  return "none";
}

bool
is_color_name (string s) {
  color c= named_color (s);
  if ((c & 0xffffff) != 0) return true;
  if (s == "black" || starts (s, "#000")) return true;
  return false;
}

color
named_color (string s, int a) {
  color c= color_from_name (locase_all (s));
  a      = (a * (c >> 24)) / 255;
  return (a << 24) + (c & 0xffffff);
}

array<int>
get_named_rgb_color (string s) {
  return get_rgb_color (named_color (s));
}

/******************************************************************************
 * Hexadecimal colors
 ******************************************************************************/

string
get_hex_color (color c) {
  int r, g, b, a;
  get_rgb_color (c, r, g, b, a);
  if (a == 255)
    return "#" * as_hexadecimal (r, 2) * as_hexadecimal (g, 2) *
           as_hexadecimal (b, 2);
  else
    return "#" * as_hexadecimal (r, 2) * as_hexadecimal (g, 2) *
           as_hexadecimal (b, 2) * as_hexadecimal (a, 2);
}

string
get_hex_color (string s) {
  return get_hex_color (named_color (s));
}

string
named_rgb_color (array<int> rgba) {
  return get_hex_color (rgb_color (rgba));
}
